﻿using Foundation.Core;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Net;

namespace Dyao.Book.Web.Extensions
{
    public static class CustomExceptionHandlerMiddlewareExtensions
    {
        public static IApplicationBuilder UseCustomExceptionHandler(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<CustomExceptionHandlerMiddleware>();
        }
    }

    public class CustomExceptionHandlerMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IWebHostEnvironment _env;
        private readonly ILogger<CustomExceptionHandlerMiddleware> _logger;
        private readonly JsonSerializerSettings _settings = new JsonSerializerSettings();
        private readonly IServiceProvider _serviceProvider;

        public CustomExceptionHandlerMiddleware(RequestDelegate next,
            IWebHostEnvironment env,
            ILogger<CustomExceptionHandlerMiddleware> logger,
            IServiceProvider serviceProvider)
        {
            _next = next;
            _env = env;
            _logger = logger;

            _settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            _settings.Formatting = Formatting.Indented;
            _serviceProvider = serviceProvider;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            var start = DateTime.Now.Ticks;
            context.Response.OnStarting(() =>
            {
                var t = TimeSpan.FromTicks(DateTime.Now.Ticks - start).TotalMilliseconds;
                context.Response.Headers["x-response-time-ms"] = t.ToString("f2");

                return Task.CompletedTask;
            });

            try
            {
                await _next(context);

            }
            catch (Exception exception)
            {
                var httpStatusCode = HttpStatusCode.InternalServerError;
                var apiStatusCode = ApiResultStatusCode.ServerError;
                var isValidateEx = exception is ValidateBusinessException;

                ApiResult result = null;

                if (!isValidateEx)
                {
                    _logger.LogError(exception, exception.Message);
                }

                if (context.Response.HasStarted)
                    throw new InvalidOperationException("The response has already started, the http status code middleware will not be executed.");

                if (isValidateEx)
                {
                    result = (exception as ValidateBusinessException).CreateApiResult();
                    httpStatusCode = HttpStatusCode.BadRequest;

                    await WriteResponseAsync(result, httpStatusCode);
                    return;
                }

                var message = exception.Message;
                string errorCode = null;

                result = new ApiResult(false, apiStatusCode, message);

                Action<string, object> addData = (key, value) => { };


                if (_env.IsDevelopment())
                {
                    var dict = new Dictionary<string, object>
                    {
                        ["StackTrace"] = exception.ToString(),
                    };

                    if (exception.InnerException != null)
                    {
                        dict.Add("InnerException.Exception", exception.InnerException.Message);
                        dict.Add("InnerException.StackTrace", exception.InnerException.StackTrace);
                    }

                    result.AddtionData = dict;

                    addData = (key, value) => dict[key] = value;
                }

                if (exception is AppException appException)
                {
                    apiStatusCode = appException.ApiStatusCode;

                    if (appException.AdditionalData != null)
                    {
                        addData("Exception.AdditionalData", appException.AdditionalData);
                    }

                    if (appException is BusinessException businessException)
                    {
                        errorCode = businessException.ErrorCode;

                        //addData("ErrorCode", errorCode);
                    }

                }
                else if (exception is SecurityTokenExpiredException
                    || exception is UnauthorizedAccessException)
                {
                    httpStatusCode = HttpStatusCode.Unauthorized;
                    apiStatusCode = ApiResultStatusCode.UnAuthorized;
                    message = ApiResultStatusCode.UnAuthorized.GetDescription();

                    if (exception is SecurityTokenExpiredException tokenException)
                    {
                        message = "认证已过期";

                        addData("Expires", tokenException.Expires.ToString());
                    }
                }
                else
                {
                    if (!_env.IsDevelopment())
                    {
                        message = "服务器内部错误";
                    }
                }

                result.Message = message;
                result.ErrorCode = errorCode;
                result.StatusCode = apiStatusCode;

                await WriteResponseAsync(result, httpStatusCode);

            }

            async Task WriteResponseAsync(ApiResult result, HttpStatusCode httpStatusCode)
            {
                var json = JsonConvert.SerializeObject(result, _settings);
                context.Response.StatusCode = (int)httpStatusCode;
                context.Response.ContentType = "application/json";
                await context.Response.WriteAsync(json);
            }

        }
    }
}
